//思路类似于朴素版的dijkstra算法，所以时间复杂度类似于朴素的dijkstra，同样用于稠密图
//更新原理是因为当一个点距离集合最近的时候，代表他现阶段已经是已经在集合的点内部更新的最好的，利用贪心的思想即可
#include <iostream>
#include <cstring>
using namespace std;

const int N = 510;
const int INF = 0x3f3f3f3f;

int g[N][N];       //稠密图用稠密图来存
int dist[N];       //记录点到集合的最短路径
bool st[N];        //标记是否进入集合
int n, m, a, b, c; //接受输入

int prim()
{
    int res = 0;
    dist[1] = 0;                 //定义第一个点到集合的路程为0
    for (int i = 1; i <= n; ++i) //循环n次，每次都是将一个点加入最短路
    {
        int t = -1;                  //本次加入集合的点未选定
        for (int j = 1; j <= n; ++j) //遍历所有点，找到现阶段距离集合最近的点
            if (!st[j] && (t == -1 || dist[j] < dist[t]))
                t = j;
        if (dist[t] == INF) //此点和主集合不连通
            return INF;     //直接返回INF表示不存在最大值
        st[t] = true;
        res += dist[t]; //能到达这里，代表这里加入的确实是一条已经存在了的边
        //这里注意，一定要先加再用它其更新其他点，不然的话，可能会通过负的自环把自己更新了，这样就会出错
        for (int j = 1; j <= n; ++j) //更新所有点，其实这里就算更新其他在集合里面的点也没关系，因为已经没意义了
            dist[j] = min(dist[j], g[t][j]);
    }
    return res; //这里返回的一定是一个正确答案值
}

int main()
{
    memset(g, 0x3f, sizeof g);       //初始化邻接矩阵，初始化为INF是因为后面使用min来
    memset(dist, 0x3f, sizeof dist); //初始化到的距离，先默认INF，后续再继续优化
    memset(st, false, sizeof st);    //现阶段集合为空
    cin >> n >> m;
    while (m--)
    {
        cin >> a >> b >> c;
        g[a][b] = min(g[a][b], c); //尝试更新邻接表
        g[b][a] = min(g[b][a], c); //尝试更新邻接表
    }
    int t = prim();
    if (t == INF)
        cout << "impossible";
    else
        cout << t;
    return 0;
}